package cn.itcast.zt.proxys;

import cn.itcast.zt.annos.ApiServer;
import cn.itcast.zt.beans.MethodInfo;
import cn.itcast.zt.beans.ServerInfo;
import cn.itcast.zt.interfaces.ProxyCreator;
import cn.itcast.zt.interfaces.RestHandler;
import cn.itcast.zt.resthandler.WebClientRestHandler;
import javafx.geometry.Pos;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 使用JDK动态代理实现代理类
 * created by zhangtian on 2018/7/9
 */
@Slf4j
public class JDKProxyCreator implements ProxyCreator {
    @Override
    public Object createProxy(Class<?> type) {
        log.info("createProxy:" +type);
        // 根据接口得到API服务器信息
        ServerInfo serverInfo = extractServerInfo(type) ;

        log.info("serverInfo:" +serverInfo);

        // 给每一个代理类实现
        RestHandler handler = new WebClientRestHandler();
        // 初始化服务器信息（初始化webclient）
        handler.init(serverInfo);

        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{type}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 根据方法和参数得到调用信息
                        MethodInfo methodInfo = extractMethodInfo(method, args) ;
                        log.info("methodInfo:" +methodInfo);
                        // 调用rest
                        return handler.invokeRest(methodInfo);
                    }
                });
    }

    /**
     * 提取服务器信息
     * @param type
     * @return
     */
    private ServerInfo extractServerInfo(Class<?> type) {
        ServerInfo serverInfo = new ServerInfo() ;
        ApiServer anno = type.getAnnotation(ApiServer.class) ;
        serverInfo.setUrl(anno.value());
        return serverInfo ;
    }

    /**
     * 根据方法定义和调用参数得到调用的相关信息
     * @param method
     * @param args
     * @return
     */
    private MethodInfo extractMethodInfo(Method method, Object[] args) {
        MethodInfo methodInfo = new MethodInfo() ;
        // 得到请求URL和请求方法
        Annotation[] annotations = method.getAnnotations() ;
        for(Annotation anno : annotations){
            if(anno instanceof GetMapping){
                GetMapping a = (GetMapping) anno ;
                methodInfo.setUrl(a.value()[0]) ;
                methodInfo.setMethod(HttpMethod.GET);
            }else if(anno instanceof PostMapping) {
                PostMapping a = (PostMapping) anno ;
                methodInfo.setUrl(a.value()[0]) ;
                methodInfo.setMethod(HttpMethod.POST);
            }else if(anno instanceof DeleteMapping){
                DeleteMapping a = (DeleteMapping) anno ;
                methodInfo.setUrl(a.value()[0]) ;
                methodInfo.setMethod(HttpMethod.POST);
            }
        }

        // 得到调用的参数和body
        Parameter[] parameters = method.getParameters() ;

        // 参数和值对应的map
        Map<String, Object> params = new LinkedHashMap<>() ;
        for(int i = 0; i < parameters.length; i++){
            // 是否带有@PathVariable注解
            PathVariable annoPath = parameters[i].getAnnotation(PathVariable.class) ;
            if(annoPath != null){
                params.put(annoPath.value(), args[i]) ;
                methodInfo.setParams(params);
            }

            // 是否带了@RequestBody
            RequestBody annoBody = parameters[i].getAnnotation(RequestBody.class) ;
            if(annoBody != null){
                methodInfo.setBody((Mono<?>)args[i]);
                // 请求对象的实际类型
                Type[] actualTypeArguments = ((ParameterizedType) (parameters[i].getParameterizedType()))
                        .getActualTypeArguments();
                methodInfo.setBodyElementType((Class<?>) actualTypeArguments[0]);
            }
        }

        // 提取返回对象信息
        // 返回Flux还是Mono
        // isAssignableFrom判断类型是否是某个类的子类
        // instanceOf判断实例是否某个类的子类
        boolean isFlux = method.getReturnType().isAssignableFrom(Flux.class) ;
        methodInfo.setReturnFlux(isFlux);

        // 得到某个对象的实际类型
        Class<?> elementType = (Class<?>)((ParameterizedType)method.getGenericReturnType()).getActualTypeArguments()[0];
        methodInfo.setReturnElementType(elementType);

        return methodInfo ;
    }
}
